package com.patternity.annotation.util;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import au.com.bytecode.opencsv.CSVReader;

/**
 * 
 */

/**
 * A generator of Java annotations, to automate the writing of many annotations
 * from a shared document.
 * 
 * This generator reads a CSV file and generates each annotation file (.java)
 * accordingly. Supports multiple attributes (comma-separated). Options to
 * generate annotations with a chosen Retention policy, a custom package prefix,
 * and to a chosen path.
 * 
 * @author Cyrille.Martraire
 */
public class AnnotationGenerator {

	private static final int NAME = 0;
	private static final int CATEGORY = 1;
	private static final int ATTRIBUTE = 3;
	private static final int BRIEF = 6;
	private static final int LINK = 7;

	/**
	 * Each possible command line keyword
	 * 
	 * @author cyrillemartraire
	 */
	public enum Option {

		RET, PKG, OUT, CSV;
	}

	/**
	 * The possible retention types
	 * 
	 * 
	 * @author cyrillemartraire
	 */
	public enum Retention {

		SOURCE, CLASS, RUNTIME;
	}

	public static void main(final String[] args) {
		final String help = "annogen [-ret SOURCE|CLASS|RUNTIME] [-pkg <package prefix, e.g com.mycompany>] [-out <path to generated files, e.g. target/gen>] <csv file, e.g. patterns.csv>";
		if (args.length == 0) {
			System.out.println(help);
		}
		try {
			final Map<Option, String> options = parseArgs(args);
			generate(options);
		} catch (final RuntimeException e) {
			e.printStackTrace();

			System.out.println(help);
		} catch (final IOException e) {
			System.err.println(e.getLocalizedMessage());
		}
	}

	private static void generate(final Map<Option, String> options)
			throws IOException {
		final String retention = options.get(Option.RET);
		final String prefix = options.get(Option.PKG);
		final File path = new File(options.get(Option.OUT));
		if (!path.exists()) {
			path.mkdirs();
		}
		final String csvFileName = options.get(Option.CSV);
		final File csvFile = new File(csvFileName);
		System.out.println("Reading file " + csvFile + " ...");
		if (!csvFile.exists()) {
			throw new IllegalArgumentException("File " + csvFile
					+ " does not exist");
		}
		final CSVReader reader = new CSVReader(new FileReader(csvFile), ',');
		final AnnotationWriter writer = new AnnotationWriter();

		int count = 0;
		for (final Object line : reader.readAll()) {
			count++;
			if (count == 1) {
				continue;// ignore headers
			}
			writer.reset();
			final String[] row = (String[]) line;
			final String annotationName = parseRow(row, writer, prefix,
					retention);
			if (annotationName == null) {
				continue;
			}
			final String category = row[1];
			// TODO path missing a separator
			final File file = AnnotationWriter.pathToFile(path.getPath(),
					prefix, category);
			writeJavaFile(file, annotationName, writer.toString());
		}
		reader.close();
		System.out.println("Generated " + count + " annotation files.");
	}

	public final static void writeJavaFile(final File dirFile,
			final String annotationName, final String content) {
		final String filename = annotationName + ".java";
		dirFile.mkdirs();

		final File file = new File(dirFile, filename);
		try {
			final BufferedWriter out = new BufferedWriter(new FileWriter(file));
			out.write(content);
			out.close();
		} catch (final IOException e) {
			System.err.println(e);
		}
	}

	private static String parseRow(final String[] row,
			final AnnotationWriter g, final String prefix,
			final String retention) {
		final String name = row[NAME];
		if (name.trim().length() == 0) {
			return null;
		}
		// TODO consider nested annotations
		if (name.isEmpty() || name.indexOf('.') != -1
				|| name.indexOf(' ') != -1) {
			return null;
		}
		// Name,Category,ElementType,Main Attribute,Type,Notes,Brief,Link
		// 0, 1 2 3 4 5 6 7
		final String category = row[CATEGORY];
		final String packageName = prefix
				+ (category.isEmpty() ? "" : "." + category);

		g.declarePackage(packageName.toLowerCase());
		g.blankLine();
		g.imports();
		g.blankLine();

		g.openJavadoc();
		g.addComment(row[BRIEF]);// brief
		final String link = row[LINK];
		if (link != null && link.trim().length() > 0) {
			g.addLink(link, name);// link
		}
		g.closeJavadoc();

		g.metaAnnotations(retention, true);
		g.declare(name);

		final String typeSpaceName = row[ATTRIBUTE];// attribute
		final String trimmed = typeSpaceName.trim();
		if (typeSpaceName != null && trimmed.length() > 0) {
			// multiple attributes (comma-separated)
			for (final String token : trimmed.split(",")) {
				g.attribute(token.trim());
			}
		}

		g.close();
		return name;
	}

	protected static Map<Option, String> parseArgs(final String[] args) {
		final Map<Option, String> options = new HashMap<Option, String>();
		options.put(Option.RET, "SOURCE");
		options.put(Option.PKG, "com.patternity.annotation");
		options.put(Option.OUT, "target/patternity/generated");

		Option current = Option.CSV;
		for (final String arg : args) {
			if (arg.startsWith("-")) {
				final String token = arg.substring(1);
				final Option option = Option.valueOf(token.toUpperCase());
				if (option != null) {
					current = option;
				}
			} else {
				options.put(current, arg);
				current = Option.CSV;
			}
		}
		return options;
	}
}
